<?php
// +----------------------------------------------------------------------------
// | PHP@妖孽 [三十年河东三十年河西,莫欺少年穷.!]
// +----------------------------------------------------------------------------
// | Copyright (c) 2015 http://www.itdaodan.com All rights reserved.
// +----------------------------------------------------------------------------
// | Author: PHP@妖孽 <dino_ma@163.com>
// +----------------------------------------------------------------------------
/**
 *@todo 微信js  sdk内支付
 +------------------------------------------------------------------------------
 * @version   1.0
 * @since 2015-5-16
 +------------------------------------------------------------------------------
 */
namespace Think\Pay\Driver;
class Wechatwappay extends  \Think\Pay\Pay{
    protected $gateway = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    protected $get_query = 'https://api.mch.weixin.qq.com/pay/orderquery';//订单查询
    protected $config  = array(
        'appid'  => '',
        'mch_id' => '',
        'key'    => '',
    	'appsecret'		=>'',//js sdk必须得有
		'notify_url'=>''
    );

    public function check() {
        if (!$this->config['appid'] || !$this->config['mch_id'] || !$this->config['key'] || !$this->config['appsecret']) {
            E("微信内支付支付设置有误！");
        }
        return true;
    }


    /**
     * 构造请求返回参数给js sdk
     * @param PayVo $vo
     * @return array
     */
    public function buildRequestForm(\Think\Pay\PayVo $vo) {
//     	$open_id = $this->getOpenId();//获取微信用户open_id
//     	if(is_array($open_id)){
//     		return $open_id;
//     	}
    	$open_id = $vo->getOpenId();
    	defined('NOW_TIME')?:define('NOW_TIME', $_SERVER['REQUEST_TIME']);
    	$param = array(
    			'device_info'=>'',
    			'body'             =>  $vo->getTitle(),//
    			'attach'		   =>json_encode($vo->getParam()),//设置附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
    			'out_trade_no'     => $vo->getOrderNo(),//订单
    			'total_fee'        => (int)($vo->getFee()*100),//价钱
    			'time_start'	   =>date('YmdHis',NOW_TIME),
    			'time_expire'	   =>date("YmdHis", NOW_TIME + 600),
    			'goods_tag'	       =>$vo->getOrderNo(),//设置商品标记，代金券或立减优惠功能的参数，说明详见代金券或立减优惠
    			'notify_url'       => $this->config['notify_url'],
    			'trade_type'       => 'JSAPI',
    			'openid'		   =>$open_id,
    			'appid'            => $this->config['appid'],
    			'mch_id'           => $this->config['mch_id'],
    			'spbill_create_ip'=>get_client_ip(),
    			'nonce_str'        => substr(md5(time()), 0, 12)
		);
    	$param['sign'] = $this->makeSign($param);
    	$xml      = static::toXml($param);
    	$response = $this->http($this->gateway, $xml);
    	$result = $this->inIt($response);
    	
    	//构造返回Js的参数
    	if(!array_key_exists("appid", $result)|| !array_key_exists("prepay_id", $result)|| $result['prepay_id'] == ""){
    		E("参数错误");
    	}
    	$time = time();
    	//构造请求参数
    	$array = array(
    			'appId'=>$result['appid'],
    			'timeStamp'=>"$time",
    			'nonceStr'=>$param['nonce_str'],
    			'package'=>'prepay_id=' . $result['prepay_id'],
    			'signType'=>'MD5'
    	);
    	
    	$time = time();
    	$array['paySign']=$this->makeSign($array);
    	$data = array();
    	$data["appid"] = $result['appid'];
    	$data["url"] = __SELF__;
    	$access_token=MsjF('msj_wx_data'.$open_id);
    	if(!$access_token){
    		$data["accesstoken"] = $access_token["access_token"];//$getData["access_token"]
    	}
    	$data["timestamp"] = $time;
    	$data["noncestr"] = substr(md5(time()), 0, 12);
    	
    	ksort($data);
    	$params = $this->_toUrlParams($data);
    	$addrSign = sha1($params);
    	
    	$afterData = array(
    			"addrSign" => $addrSign,
    			"signType" => "sha1",
    			"scope" => "jsapi_address",
    			"appId" => $this->config['appid'],
    			"timeStamp" => "{$time}",
    			"nonceStr" => substr(md5(time()), 0, 12)
    	);
    	$parameters = json_encode($afterData);
		return array('code' => json_encode($array),'address'=>$parameters, 'type' => 'jssdk');
    }
	
    /**
     *
     * 拼接签名字符串
     * @param array $urlObj
     *
     * @return 返回已经拼接好的字符串
     */
    private static function _testToUrlParams($param){
    	$buff = "";
    	foreach ($param as $k => $v)
    	{
    		if($k != "sign" && $v != "" && !is_array($v)){
    			$buff .= strtolower($k) . "=" . $v . "&";
    		}
    	}
    	if (strlen($buff) > 0){
			$buff = substr($buff, 0, strlen($buff)-1);
		}
    	return $buff;
    }
    
   
    
    
    /**
     * 生成签名
     * @param $param
     * @return string
     */
    private function generateSign($param) {
    	if(!empty($param)){
    		ksort($param);
    		reset($param);
    		$arg = '';
    		foreach ($param as $key => $value) {
    			if ($value && $key != 'sign') {
    				$arg .= "$key=$value&";
    			}
    		}
    		return strtoupper(md5($arg . 'key=' . $this->config['key']));
    	}else{
    		return '';
    	}
    }
    
    /**
     * 数组转XML
     * @param $values
     * @return string
     * @throws \Think\Exception
     */
    private static function toXml($values) {
    	if (!is_array($values) || count($values) <= 0) {
    		E("数组数据异常！");
    	}
    	$xml = "<xml>";
    	foreach ($values as $key => $val) {
    		if (is_numeric($val)) {
    			$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
    		} else {
    			$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
    		}
    	}
    	$xml .= "</xml>";
    	return $xml;
    }
    
    
    /**
     * @todo 制作签名
     */
    private function makeSign($param){
    	ksort($param);
    	$string = self::_toUrlParams($param);
    	$string = $string . "&key=".$this->config['key'];
    	$string = md5($string);
    	$result = strtoupper($string);
    	return $result;
    }
    
    

    
    
    /**
     * 以post方式提交xml到对应的接口url
     */
    private static function http($url,$xml, $useCert = false, $second = 30){
    	$ch = curl_init();
    	//设置超时
    	curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    
    	//如果有配置代理这里就设置代理
    	curl_setopt($ch,CURLOPT_URL, $url);
    	curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
    	curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
    	//设置header
    	curl_setopt($ch, CURLOPT_HEADER, FALSE);
    	//要求结果为字符串且输出到屏幕上
    	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    
    	if($useCert == true){
    		//设置证书
    		//使用证书：cert 与 key 分别属于两个.pem文件
    		// 			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
    		// 			curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
    		// 			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
    		// 			curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
    	}
    	//post提交方式
    	curl_setopt($ch, CURLOPT_POST, TRUE);
    	curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    	//运行curl
    	$data = curl_exec($ch);
    	//返回结果
    	if($data){
    		curl_close($ch);
    		return $data;
    	} else {
    		$error = curl_errno($ch);
    		curl_close($ch);
    	}
    }
    
    
    /**
     * 支付通知验证
     * @param $notify string
     * @return array|bool
     */
    public function verifyNotify($notify) {
    	if(!array_key_exists('transaction_no', $notify)){
    		E('微信支付交易号不存在');
    	}
    	$transaction_no= $notify['transaction_no'];
    	
    	$param = array(
    			'appid'=>$this->config['appid'],
    			'mch_id'=>$this->config['mch_id'],
    			'nonce_str'=>substr(md5(time()), 0, 12),
    			'transaction_id'=>$transaction_no
    	);
    	$param['sign']=$this->makeSign($param);
    	
    	$xml      = static::toXml($param);
    	$response = $this->http($this->$get_query, $xml);
    	$result = $this->inIt($response);
    	if(array_key_exists("return_code", $result)&& array_key_exists("result_code", $result)&& $result["return_code"] == "SUCCESS"&& $result["result_code"] == "SUCCESS"){
    		\Think\Log::write('_+_+====='.json_encode($result));
    		return true;
    	}
    	return false;
    }
	
    
    /**
     * @todo 获取微信用户openid
     */
    public function makeOpenId($array=array()){
    	//通过code获得openid
    	if (!isset($_GET['code'])){
//     		$baseUrl = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']);
    		$baseUrl = 'http://'.$_SERVER['HTTP_HOST'].__SELF__.'?para='.I('request.para');
    		$url = $this->__createOauthUrlForCode($baseUrl);
    		//给客户端勾践html然后做返回值
    		return array('url'=>$url,'type'=>'url');
    		
//     		header("Location: {$url}");
//     		$a = $this->curlGet($url);
    	} else {
    		\Think\log::write('param:'.json_encode($array));
    		//获取code码，以获取openid
    		$code = I('get.code');
    		$openid = $this->getOpenidFromMp($code);
    		\Think\log::write('openid:'.$openid);
    		$array['url'].='&open_id='.$openid.'&msj='.md5($array['client_order_no']);
    		\Think\log::write('array:'.json_encode($array));
    		header("Location: {$array['url']}");
    		exit();
    		
    	}
    }
    
    
    /**
     *
     * 构造获取code的url连接
     * @param string $redirectUrl 微信服务器回跳的url，需要url编码
     *
     * @return 返回构造好的url
     */
    private function __createOauthUrlForCode($redirectUrl) {
    	$urlObj["appid"] = $this->config['appid'];
    	$urlObj["redirect_uri"] = "$redirectUrl";
    	$urlObj["response_type"] = "code";
    	$urlObj["scope"] = "snsapi_base";
    	$urlObj["state"] = "STATE"."#wechat_redirect";
    	$bizString = $this->_toUrlParams($urlObj);
    	return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString;
    }
    
    /**
     *
     * 拼接签名字符串
     * @param array $urlObj
     *
     * @return 返回已经拼接好的字符串
     */
    private static function _toUrlParams($param){
    	$buff = "";
		foreach ($param as $k => $v)
		{
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		$buff = trim($buff, "&");
		return $buff;
    }

    
    /**
     *
     * 通过code从工作平台获取openid机器access_token
     * @param string $code 微信跳转回来带上的code
     *
     * @return openid
     */
    public function getOpenidFromMp($code){
    	$url = $this->__createOauthUrlForOpenid($code);
    	$res = $this->curlGet($url);
    	//取出openid
    	$data = json_decode($res,true);
    	if(array_key_exists('errcode', $data)){
    		E('code获取有误'.$data['errmsg']);
    	}
    	MsjF('msj_wx_data'.$data['openid'],$data,600,RUNTIME_PATH);
    	$openid = $data['openid'];
    	return $openid;
    }
    
    /**
     * @todo curl get操作
     * @param string $url
     * @return mixed
     */
    private static function curlGet($url){
    	//初始化curl
    	$ch = curl_init();
    	//设置超时
    	curl_setopt($ch, CURLOPT_TIMEOUT,30);
    	curl_setopt($ch, CURLOPT_URL, $url);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
    	curl_setopt($ch, CURLOPT_HEADER, FALSE);
    	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    	//运行curl，结果以json形式返回
    	$res = curl_exec($ch);
    	curl_close($ch);
    	return $res;
    }
    
    /**
     *
     * 构造获取open和access_toke的url地址
     * @param string $code，微信跳转带回的code
     *
     * @return 请求的url
     */
    private function __createOauthUrlForOpenid($code){
    	$urlObj['appid'] = $this->config['appid'];
    	$urlObj['secret'] = $this->config['appsecret'];
    	$urlObj['code'] = $code;
    	$urlObj['grant_type'] = 'authorization_code';
    	$bizString = $this->_toUrlParams($urlObj);
    	return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
    }
    
    /**
     * @todo 初始化xml
     */
    private function Init($xml){
    	if(!$xml){
    		E('xml数据有问题~');
    	}
    	$xml = $this->xml2arr($xml);
    	if(array_key_exists('return_code', $xml)){
    		if($xml['return_code']=='FAIL'){
    			E($xml['return_msg']);
    		}
    	}
    	$xml = $this->checkSign($xml);
    	return $xml;
    }
    
    /**
     * @todo 把xml处理成数组
     */
    private static function xml2arr($xml){
    	if(!$xml){
    		E("xml数据异常！");
    	}
    	//将XML转为array
    	return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }
    
    /**
     * @todo 检测签名
     */
    private function checkSign($array){
    	if(!$this->IsSignSet($array)){
    		E("签名数据异常！2");
    	}
    	$old_sign = $array['sign'];
    	unset($array['sign']);
    	$sign = $this->makeSign($array);
    	if($old_sign=== $sign){
    		return $array;
    	}
    	E("签名数据错误！1");
    }
    
    /**
     * @todo 检测是否设置签名
     */
    private static function isSignSet($array){
    	return array_key_exists('sign', $array);
    }
    
    /**
     * @todo 返回签名
     */
    private static function getSign($param){
    	return $param['sign'];
    }
    
    /**
     * @todo 查询订单状态
     * @param \Think\Pay\PayVo $vo
     */
    public function getOrderQuery(\Think\Pay\PayVo $vo) {
        $param = array(
            'appid'            => $this->config['appid'],
            'mch_id'           => $this->config['mch_id'],
            'nonce_str'        => substr(md5(time()), 0, 12),
            'out_trade_no'     => $vo->getOrderNo(),
        );
        $param['sign'] = $this->generateSign($param);
        $xml      = static::toXml($param);
        $response = $this->http('https://api.mch.weixin.qq.com/pay/orderquery', $xml);
        $result   = self::fromXml($response);
        if ($result['return_code'] == 'FAIL')
            E($result['return_msg']);
            if ($result['result_code'] == 'FAIL')
                E($result['err_code'] . ' ' . $result['err_code_des']);
                if ($result['sign'] == $this->generateSign($result)) {
                    return array(
                        'out_trade_no' => $result['out_trade_no'], //支付网关订单号
                        'transaction_id' => $result['transaction_id'],//交易号
                        'attach'=>$result['attach'],//附加参数
                        'money'=>$result['cash_fee']/100,//交易金额
                    );
                } else {
                    E($response);
                }
        
    }
    
}